package ca.psquared.superbrixgame.states;

import ca.psquared.superbrixgame.*;
import ca.psquared.superbrixgame.key.KeyAction;
import ca.psquared.superbrixgame.key.KeyHandler;
import ca.psquared.superbrixgame.scheduler.ITaskHandle;
import ca.psquared.superbrixgame.scheduler.IntervalTask;
import ca.psquared.superbrixgame.scheduler.Scheduler;
import org.newdawn.slick.*;
import org.newdawn.slick.geom.Rectangle;
import org.newdawn.slick.state.BasicGameState;
import org.newdawn.slick.state.StateBasedGame;
import org.newdawn.slick.state.transition.BlobbyTransition;
import org.newdawn.slick.state.transition.EmptyTransition;
import org.newdawn.slick.state.transition.FadeInTransition;
import org.newdawn.slick.state.transition.RotateTransition;

import java.util.ArrayList;
import java.util.List;

public class MainGameState extends BasicGameState {

    private QueuedPieceProvider pieceProvider1;
    private QueuedPieceProvider pieceProvider2;

    private IWorld world;
    private IPiece currentPiece;

    private KeyHandler keyHandler;

    private Scheduler scheduler;
    private boolean pauseState;
    private IntervalTask moveDownTask;
    private final List<IBlock> landedBlocks = new ArrayList<IBlock>();
    private final List<IBlock> completedRows = new ArrayList<IBlock>();

    private int[] rowBlockCount;
    private final Rectangle gameArea;
    private StateBasedGame game;
    private ScoreCounter scoreCounter;

    public MainGameState() {

        final int width = (GameSettings.GRID_COLUMNS * GameSettings.BLOCK_SIZE) + 2;
        final int height = (GameSettings.GRID_ROWS * GameSettings.BLOCK_SIZE) + 2;
        gameArea = new Rectangle(0, 0, width, height);
    }

    @Override
    public int getID() {
        return SuperBrixGame.MAIN_GAME_STATE;
    }

    @Override
    public void init(GameContainer gameContainer, StateBasedGame game) throws SlickException {

        this.game = game;
        pauseState = false;
        keyHandler = new KeyHandler();
        scheduler = new Scheduler();
        world = new World();
        scoreCounter = new ScoreCounter();

        registerActionKeyRight();
        registerActionKeyLeft();
        registerActionKeyUp();
        registerActionKeySpace();
        registerActionKeyPause();
        registerActionKeyDown();
        final Sound moveDownSound = new Sound ("C:\\Windows\\Media\\Windows Navigation Start.wav");
        final Sound moveSound = new Sound ("C:\\Windows\\Media\\Windows Startup.wav")  ;

        moveDownTask = new IntervalTask(700) {
            @Override
            protected void onExecute(GameContainer gameContainer, int delta) throws SlickException {
                currentPiece.moveDown();
                moveDownSound.play();
            }
        };
        scheduler.addTask(moveDownTask);
    }

    @Override
    public void enter(GameContainer container, StateBasedGame game) throws SlickException {
        initPieceProviders();
        newPiece();
        landedBlocks.clear();
    }

    private void initPieceProviders() {
        pieceProvider2 = new QueuedPieceProvider(new PieceProvider());
        pieceProvider1 = new QueuedPieceProvider(pieceProvider2);
    }

    private void newPiece() {
        currentPiece = pieceProvider1.getPiece();
        currentPiece.setWorld(world);
        centerPiece(currentPiece);
    }

    private void centerPiece(IPiece currentPiece) {
        int width = currentPiece.getWidth();
        final int offset = (GameSettings.GRID_COLUMNS - width) / 2;

        for (IBlock block : currentPiece.getBlocks()) {
            block.setX(block.getX()+offset);
        }
    }

    @Override
    public void update(GameContainer gameContainer, StateBasedGame game, int delta) throws SlickException {

        if (!pauseState) {
            scheduler.update(gameContainer, delta);
        }
    }

    @Override
    public void render(GameContainer container, StateBasedGame game, Graphics graphics) throws SlickException {

        // Offset everything
        graphics.translate(150, 50);
        // Draw rectangle around blocks area
        graphics.setColor(Color.gray);
        graphics.draw(gameArea);

        // Clip around game area
        graphics.setWorldClip(gameArea);

        // Offset block rendering by 0,1 (not sure why, but it lines up this way)
        graphics.translate(0, 1);

        // Render current piece, if any
        if (currentPiece != null) {
            currentPiece.render(graphics);
        }

        // Render all landed blocks
        for (IBlock landedBlock : landedBlocks) {
            landedBlock.render(graphics);
        }

        // Clear game area clip
        graphics.clearWorldClip();

        // Rectangle around next piece
        graphics.translate(350, 0);
        pieceProvider1.render(graphics);

        graphics.translate(0, 180);
        pieceProvider2.render(graphics);

        graphics.translate(0, 180);
        scoreCounter.render(graphics);
    }

    public void keyPressed(int key, char c) {
        keyHandler.keyPressed(key);
    }

    public void keyReleased(int key, char c) {
        keyHandler.keyReleased(key);
    }

    public void registerActionKeyRight() {
        keyHandler.registerUserInputAction(new KeyAction(Input.KEY_RIGHT) {
            ITaskHandle moveRightHandle;

            public void onKeyPressed() {
                if (!pauseState) {
                    currentPiece.moveRight();
                    moveRightHandle = scheduler.addTask(new IntervalTask(300, 50) {
                        @Override
                        protected void onExecute(GameContainer gameContainer, int delta) throws SlickException {
                            currentPiece.moveRight();
                        }
                    });
                }
            }

            public void onKeyReleased() {
                if (!pauseState) {
                    moveRightHandle.remove();
                }
            }
        });
    }

    public void registerActionKeyLeft() {

        keyHandler.registerUserInputAction(new KeyAction(Input.KEY_LEFT) {
            ITaskHandle moveLeftHandle;

            public void onKeyPressed() {
                if (!pauseState) {
                    currentPiece.moveLeft();
                    moveLeftHandle = scheduler.addTask(new IntervalTask(300, 50) {
                        @Override
                        protected void onExecute(GameContainer gameContainer, int delta) throws SlickException {
                            currentPiece.moveLeft();
                        }
                    });
                }
            }

            public void onKeyReleased() {
                if (!pauseState) {
                    moveLeftHandle.remove();
                }
            }
        });
    }

    public void registerActionKeyUp() {
        keyHandler.registerUserInputAction(new KeyAction(Input.KEY_UP) {
            public void onKeyPressed() {
                if (!pauseState) {
                    currentPiece.rotateCw();
                }
            }
        });
    }

    public void registerActionKeySpace() {
        keyHandler.registerUserInputAction(new KeyAction(Input.KEY_SPACE) {
            @Override
            protected void onKeyPressed() {
                if (!pauseState) {
                    currentPiece.rotateCCw();
                }
            }
        });
    }

    public void registerActionKeyPause() {
        keyHandler.registerUserInputAction(new KeyAction(Input.KEY_PAUSE) {
            public void onKeyPressed() {
                pauseState = !pauseState;
            }
        });
    }

    public void registerActionKeyDown() {
        keyHandler.registerUserInputAction(new KeyAction(Input.KEY_DOWN) {
            private int previousInterval;

            @Override
            protected void onKeyPressed() {
                previousInterval = moveDownTask.getInterval();
                moveDownTask.setInterval(50);
                moveDownTask.expireTimer();
                scoreCounter.setPieceSpeedFast();
            }

            @Override
            protected void onKeyReleased() {
                moveDownTask.setInterval(700);
                scoreCounter.setPieceSpeedSlow();
            }
        });
    }

    private class World implements IWorld {

        @Override
        public boolean doesCollide(int x, int y) {
            if (x < 0 || x >= GameSettings.GRID_COLUMNS) {
                return true;
            }
            if (y >= GameSettings.GRID_ROWS) {
                return true;
            }
            for (IBlock block : landedBlocks) {
                if (x == block.getX() && y == block.getY()) {
                    return true;
                }
            }
            return false;
        }

        @Override
        public void pieceHasLanded(IPiece piece) {
            if (checkCompletedWorld(piece)) {
                fillWorld();
            } else {
                moveToLandedBlocks(piece);
                processCompletedRow();
                queueNewPiece();
            }
        }

        private void queueNewPiece() {
            scheduler.addExclusiveTask(new IntervalTask(200, 0, 0) {
                @Override
                protected void onExecute(GameContainer gameContainer, int delta) throws SlickException {
                    newPiece();
                }
            });
        }

        private boolean checkCompletedWorld(IPiece piece) {
            for (IBlock block : piece.getBlocks()) {
                if (block.getY() == 0) {
                    return true;
                }
            }
            return false;
        }

        public void moveToLandedBlocks(IPiece piece) {
            landedBlocks.addAll(piece.getBlocks());
        }

        public void processCompletedRow() {
            boolean completedRowExists = false;
            rowBlockCount = new int[GameSettings.GRID_ROWS];
            for (IBlock block : landedBlocks) {
                rowBlockCount[block.getY()]++;
            }
            scoreCounter.beginCount();
            completedRows.clear();
            for (int i = 0; i < GameSettings.GRID_ROWS; i++) {
                if (rowBlockCount[i] == GameSettings.GRID_COLUMNS) {
                    scoreCounter.addLineCleared();
                    completedRowExists = true;
                    for (IBlock block : landedBlocks) {
                        if (block.getY() == i) {
                            completedRows.add(block);
                        }
                    }
                }
            }
            if (completedRowExists) {
                blinkRow();
                removeCompletedRows();
            }
            scheduler.addExclusiveTask(new IntervalTask(0, 0, 0) {
                @Override
                protected void onExecute(GameContainer gameContainer, int delta) throws SlickException {
                    scoreCounter.endCount();
                }
            });
        }

        private void removeCompletedRows() {

            scheduler.addExclusiveTask(new IntervalTask(0, 0, 0) {
                @Override
                protected void onExecute(GameContainer gameContainer, int delta) throws SlickException {
                    landedBlocks.removeAll(completedRows);

                    for (int i = 0; i < GameSettings.GRID_ROWS; i++) {
                        if (rowBlockCount[i] == GameSettings.GRID_COLUMNS) {
                            for (IBlock block : landedBlocks) {
                                if (block.getY() < i) {
                                    block.setY(block.getY() + 1);
                                }
                            }
                        }
                    }

                }
            });
        }

        public void blinkRow() {

            scheduler.addExclusiveTask(new IntervalTask(0, 150, 600) {

                private boolean transparent = true;

                @Override
                protected void onExecute(GameContainer gameContainer, int delta) throws SlickException {

                    final float alpha = transparent ? 0f : 1f;

                    for (IBlock block : completedRows) {
                        block.getImage().setAlpha(alpha);
                    }
                    transparent = !transparent;
                }
            });
        }

        public void fillWorld() {

            landedBlocks.clear();

            scheduler.addExclusiveTask(new IntervalTask(0, 30, -1) {

                private Image image = ImageFactory.getImage(Color.orange);
                int y = GameSettings.GRID_ROWS;

                @Override
                protected void onExecute(GameContainer gameContainer, int delta) throws SlickException {

                    y--;
                    for (int x = 0; x < GameSettings.GRID_COLUMNS; x++) {
                        landedBlocks.add(new Block(image, x, y));
                    }

                }

                @Override
                public boolean isDone() {
                    return y == 0;
                }
            });
            scheduler.addExclusiveTask(new IntervalTask(500, 0, 0) {
                @Override
                protected void onExecute(GameContainer gameContainer, int delta) throws SlickException {
                    game.enterState(SuperBrixGame.GAME_END_STATE, new EmptyTransition(), new FadeInTransition());
                }
            });

        }
    }


}
